﻿#define _debug

using System;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware;
using SecretLabs.NETMF.Hardware.Netduino;
using System.IO.Ports;
using System.Text;
using System.Diagnostics;

namespace Yaam.Netduino
{
    /// <summary>
    /// Encoding for how the LEDs should behave.
    /// </summary>
    public enum LEDEffect : byte
    {
        Solid = 0x04,
        Throb = 0x08,
        Blink = 0x10,
        SOS   = 0xF0
    }

    /// <summary>
    /// Decoded representation of payload received from the serial port.
    /// </summary>
    public class DecodedColorAction
    {
        #region Private Members

        private byte _red;
        private byte _green;
        private byte _blue;
        private byte _state;
        private String _encodedPayload;

        #endregion 

        #region Accessors
        /// <summary>
        /// Color value for the Red LED
        /// </summary>
        public byte Red
        {
            get
            {
                return _red;
            }
        }

        /// <summary>
        /// Color value for the Green LED
        /// </summary>
        public byte Green
        {
            get
            {
                return _green;
            }
        }

        /// <summary>
        /// Color value for the Blue LED
        /// </summary>
        public byte Blue
        {
            get
            {
                return _blue;
            }
        }

        /// <summary>
        /// Encoded LED state
        /// </summary>
        public byte State
        {
            get
            {
                return _state;
            }
        }

        public byte Effect
        {
            get
            {
                return (byte)(_state & 0xFC);
            }
        }

        #endregion

        #region Public Methods

        public DecodedColorAction()
        {
            _red = 0;
            _green = 0;
            _blue = 0;
            _state = 0;
            _encodedPayload = "#00000000";
        }

        public DecodedColorAction(string encodedPayload)
        {
            _encodedPayload = encodedPayload;
            Decode();
        }

        /// <summary>
        /// String representation of the encoded payload.
        /// </summary>
        /// <returns>String</returns>
        public override string ToString()
        {
            return _encodedPayload;
        }

        /// <summary>
        /// Changes the properties of the DecodedColorAction object based on the incoming encoded payload.
        /// </summary>
        /// <param name="encodedPayload">String</param>
        public void Change(string encodedPayload)
        {
            _encodedPayload = encodedPayload;
            Decode();
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Converts character to hexadecimal representation.
        /// </summary>
        /// <param name="c">char between [0,9] or [A,F]</param>
        /// <returns>byte representing char. 0 if the char is out the range of [0,9] or [A,F].</returns>
        private static byte Hex2Dec(char c)
        {
            if (c >= '0' && c <= '9')
            {
                return (byte)(c - '0');
            }
            else if (c >= 'A' && c <= 'F')
            {
                return (byte)(c - 'A' + 10);
            }
            else
            {
                return 0;
            }
        }

        private void Decode()
        {
            byte state = 0;

            _red = (byte)(Hex2Dec(_encodedPayload[2]) + Hex2Dec(_encodedPayload[1]) * 16);
            _green = (byte)(Hex2Dec(_encodedPayload[4]) + Hex2Dec(_encodedPayload[3]) * 16);
            _blue = (byte)(Hex2Dec(_encodedPayload[6]) + Hex2Dec(_encodedPayload[5]) * 16);
            state = (byte)(Hex2Dec(_encodedPayload[8]) + Hex2Dec(_encodedPayload[7]) * 16);

            _state = state;
        }

        #endregion
    }

    public class ColorChanger
    {
        #region Public Members

        public const Int32 YAAM_BUFFER_LENGTH = 9; // #00000000
        public PWM LED_Red;
        public PWM LED_Green;
        public PWM LED_Blue;
        public OutputPort LED0;
        public OutputPort LED1;
        public SerialPort _serialPort;
        public DecodedColorAction _decodedPayload;

        #endregion

        #region Private Members

        private Timer _throbTimer = null;
        private TimerCallback _throbTimerDelegate;
        private bool _cancelled = false;

        #endregion

        #region Constructors

        public ColorChanger()
        {
            _decodedPayload = new DecodedColorAction();
            _throbTimerDelegate = new TimerCallback(Throb);
            _serialPort = new SerialPort(SerialPorts.COM1, 9600, Parity.None, 8, StopBits.One);
            _serialPort.ReadTimeout = 10;
            _serialPort.DataReceived += new SerialDataReceivedEventHandler(SerialPort_DataReceived);
            _serialPort.Open();
        }

        #endregion

        #region Private Members

        /// <summary>
        /// Mixes the RGB colors and sets the LEDs via PWM.
        /// </summary>
        private void MixAndSetColors(byte redValue, byte greenValue, byte blueValue)
        {
            this.LED_Red.SetDutyCycle((uint)100 * redValue / 255);
            this.LED_Blue.SetDutyCycle((uint)100 * blueValue / 255);
            this.LED_Green.SetDutyCycle((uint)100 * greenValue / 255);
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Sets the LED(s) to a specific color value.
        /// </summary>
        public void Solid(DecodedColorAction decodedColorAction)
        {
            DateTime timeStart = DateTime.Now;
            DateTime timeEnd;
            Debug.Print("[" + timeStart.ToString() + "] Enter Solid: " + decodedColorAction.ToString());

            MixAndSetColors(decodedColorAction.Red, decodedColorAction.Green, decodedColorAction.Blue);

            this.LED0.Write((int)(decodedColorAction.State & 0x01) == 0x01);
            this.LED1.Write((int)(decodedColorAction.State & 0x02) == 0x02);

            timeEnd = DateTime.Now;
            Debug.Print("[" + timeEnd.ToString() + "] Exit Solid (" + (timeEnd - timeStart).Duration().ToString() + ")");
        }
        
        /// <summary>
        /// Gently fades the LED(s) on and off.
        /// </summary>
        /// <param name="stateInfo">DecodedColorAction object</param>
        public void Throb(object stateInfo)
        {
            DateTime timeStart = DateTime.Now;
            DateTime timeEnd;
            DecodedColorAction decodedColorAction = stateInfo as DecodedColorAction;
            float maxVal = 255;
            byte i = 0;
            bool countDown = false;

            Debug.Print("[" + timeStart.ToString() + "] Enter Throb: " + decodedColorAction.ToString());

            //
            // Determine which LED(s) should be enabled
            //

            this.LED0.Write((int)(decodedColorAction.State & 0x01) == 0x01);
            this.LED1.Write((int)(decodedColorAction.State & 0x02) == 0x02);

            do
            {
                if (_cancelled)
                {
                    Debug.Print("[" + DateTime.Now + "] Cancelled Throb");
                    return;
                }

                MixAndSetColors((byte)(i * (decodedColorAction.Red / maxVal)), (byte)(i * (decodedColorAction.Green / maxVal)), (byte)(i * (decodedColorAction.Blue / maxVal)));
                //Solid((byte)(i * (_encodedPayload.Red / maxVal)), (byte)(i * (_encodedPayload.Green / maxVal)), (byte)(i * (_encodedPayload.Blue / maxVal)));

                if (true == countDown)
                {
                    Thread.Sleep(5);
                    i--;
                }
                else
                {
                    Thread.Sleep(10);
                    i++;

                    if (255 == i)
                    {
                        countDown = true;
                    }
                }
            } while (i > 0);

            MixAndSetColors(0, 0, 0);
            //Thread.Sleep(5);

            timeEnd = DateTime.Now;
            Debug.Print("[" + timeEnd.ToString() + "] Exit Throb (" + (timeEnd - timeStart).Duration().ToString() +")" );
        }
        
        /// <summary>
        /// Event handler for when serial port gets data. This determines how the LEDs should be lit.
        /// </summary>
        public void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            string payload;
            byte[] buffer = new byte[YAAM_BUFFER_LENGTH];
            int bytesReceived = _serialPort.Read(buffer, 0, YAAM_BUFFER_LENGTH);

            if (buffer[0] == '#')
            {
                payload = new String(Encoding.UTF8.GetChars(buffer));
                if (payload != _decodedPayload.ToString())
                {
                    _decodedPayload.Change(payload);

                    if (_throbTimer != null)
                    {
                        if (_decodedPayload.State == 0)
                        {
                            _cancelled = true;
                        }

                        _throbTimer.Dispose();
                        Thread.Sleep(200);
                        _cancelled = false;
                    }

                    switch (_decodedPayload.Effect)
                    {
                        case (byte)LEDEffect.Solid:
                            Solid(_decodedPayload);
                            break;

                        case (byte)LEDEffect.Throb:
                            _throbTimer = new Timer(_throbTimerDelegate, _decodedPayload, 0, 5);
                            break;

                        default:
                            Solid(_decodedPayload);
                            break;
                    }
                }
            }

            //  Notes
            //      o see http://basbrun.com/tag/net/ for a good example
            //      o for the code above, see if buffer.length is always YAAM_BUFFER_LENGTH or if it changes if the data 
            //          received is less.  If more, then does the buffer need to be flushed each time (since we only
            //          care about YAAM_BUFFER_LENGTH exactly).
        }

        public static void Main()
        {
            ColorChanger colorChanger = new ColorChanger();

            colorChanger.LED_Red = new PWM(Pins.GPIO_PIN_D5);
            colorChanger.LED_Green = new PWM(Pins.GPIO_PIN_D6);
            colorChanger.LED_Blue = new PWM(Pins.GPIO_PIN_D9);
            colorChanger.LED0 = new OutputPort(Pins.GPIO_PIN_D3, false);
            colorChanger.LED1 = new OutputPort(Pins.GPIO_PIN_D4, false);

            Thread.Sleep(Timeout.Infinite);

        }

        #endregion
    }
}
